﻿using System;
using System.Net;
using System.IO;
using System.Threading;
using System.IO.IsolatedStorage;
using System.Xml.Serialization;

namespace ScheduleMVVM.Model.Miscellaneous
{
    public class SyncableXml<T> : IDisposable
        where T : class
    {
        protected Mutex valueLock;
        protected IsolatedStorageFile isolatedStorage;
        protected System.IO.Stream xmlStream;
        protected XmlSerializer xmlSerializer;
        protected T _value;
        protected Boolean disposed;
        /// <summary>
        /// Returns whether Value property can be written
        /// </summary>
        public Boolean IsReadOnly
        { get; protected set; }
        /// <summary>
        /// Returns whether Value property can be read
        /// </summary>
        public Boolean DataPresent
        { get; protected set; }
        /// <summary>
        /// Gets or sets inner value
        /// </summary>
        /// <exception cref="ObjectDisposedException"/>
        /// <exception cref="Exception">Thrown if Value has no data</exception>
        /// <exception cref="AccessViolationException">Read only exception</exception>
        public T Value
        {
            get
            {
                __checks();
                return _value;
            }
            set
            {
                if (IsReadOnly) throw new AccessViolationException("This SyncableXml is readonly");
                valueLock.WaitOne();
                DataPresent = true;
                _value = value;
                DataAcquireTime = DateTime.Now;
                valueLock.ReleaseMutex();
            }
        }
        /// <summary>
        /// Returns either the name of the file open or "$stream$"
        /// </summary>
        public String Filename
        { get; protected set; }
        /// <summary>
        /// Returns last sync time
        /// </summary>
        public DateTime LastSyncTime
        { get; protected set; }
        /// <summary>
        /// Return data acquire time. If data is not present, returns 00.00.0000
        /// </summary>
        public DateTime DataAcquireTime
        { get; protected set; }
        public SyncableXml(String FileName)
        {
            Filename = FileName;
            __init();
            Load();
            IsReadOnly = false;
        }
        public SyncableXml(String FileName, Boolean isReadOnly)
        {
            Filename = FileName;
            __init();
            Load();
            IsReadOnly = isReadOnly;
        }
        public SyncableXml(System.IO.Stream FileStream)
        {
            Filename = "$stream$";
            __init();
            xmlStream = FileStream;
            if (xmlStream.Length > 0) // if has data
                _value = (T)xmlSerializer.Deserialize(xmlStream);
            //// Properties
            LastSyncTime = DateTime.Now;
            DataAcquireTime = new DateTime(0);
            DataPresent = true;
            IsReadOnly = false;
        }
        public SyncableXml(System.IO.Stream FileStream, Boolean isReadOnly)
        {
            Filename = "$stream$";
            __init();
            xmlStream = FileStream;
            if (xmlStream.Length > 0) // if has data
                _value = (T)xmlSerializer.Deserialize(xmlStream);
            //// Properties
            LastSyncTime = DateTime.Now;
            DataAcquireTime = new DateTime(0);
            DataPresent = true;
            IsReadOnly = isReadOnly;
        }
        private void __init()
        {
            DataPresent = false;
            disposed = false;
            xmlSerializer = new XmlSerializer(typeof(T));
#if WINDOWS_PHONE
            isolatedStorage = IsolatedStorageFile.GetUserStoreForApplication();
#endif
            valueLock = new Mutex(false, "syncable_" + Filename);
        }
        public void CopyFromStream(System.IO.Stream Stream)
        {
            Value = (T)xmlSerializer.Deserialize(Stream);
        }
        protected void Load()
        {
            // Check if the file exists
#if WINDOWS_PHONE
            if (isolatedStorage.FileExists(Filename))
            {

                xmlStream = isolatedStorage.OpenFile(Filename, System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite);
#else
            if(File.Exists(Filename))
            {
                xmlStream = File.Open(Filename, System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite);
#endif
                if (xmlStream.Length > 0)
                {
                    _value = (T)xmlSerializer.Deserialize(xmlStream);
#if WINDOWS_PHONE
                    LastSyncTime = isolatedStorage.GetLastWriteTime(Filename).DateTime;
#else
                    LastSyncTime = File.GetLastWriteTime(Filename);
#endif
                    DataAcquireTime = LastSyncTime;
                    DataPresent = true;
                }
                else LastSyncTime = DateTime.Now;
            }
            else
            {
#if WINDOWS_PHONE
                xmlStream = isolatedStorage.OpenFile(Filename, System.IO.FileMode.CreateNew, System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite);
#else
                xmlStream = File.Open(Filename, System.IO.FileMode.CreateNew, System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite);
#endif
                LastSyncTime = DateTime.Now;
                DataAcquireTime = new DateTime(0);
            }
        }
        /// <summary>
        /// Syncs Value and XML File/Steam
        /// </summary>
        public void Sync()
        {
            Erase();
            xmlStream.Seek(0, System.IO.SeekOrigin.Begin);
            __checks();
            valueLock.WaitOne();
            try
            {
                xmlSerializer.Serialize(xmlStream, _value);
                LastSyncTime = DateTime.Now;
            }
            finally
            {
                valueLock.ReleaseMutex();
            }
        }
        /// <summary>
        /// Dumps wrapped value to file. Use after Sync.
        /// </summary>
        public void Dump()
        {
            if (Filename != "$stream$")
            {
                xmlStream.Close();
                xmlStream = isolatedStorage.OpenFile(Filename, System.IO.FileMode.Open, System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite);
            }
        }
        /// <summary>
        /// Closes file/stream
        /// </summary>
        public void Close()
        {
            Dispose(true);
        }
        /// <summary>
        /// Disposes all resources used by this instance
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
        }
        private void Dispose(Boolean disposing)
        {
            if (disposing)
            {
                Sync();
                xmlStream.Dispose();
                disposed = true;
            }
        }
        ~SyncableXml()
        {
            Dispose(false);
        }
        private void Erase()
        {
            xmlStream.Seek(0, System.IO.SeekOrigin.Begin);
            for (int i = 0; i < xmlStream.Length; i++) xmlStream.WriteByte(0);
        }
        private void __checks()
        {
            if (disposed) throw new ObjectDisposedException(this.Filename);
            if (!DataPresent) throw new Exception("Property not initialized");
        }
    }
#if UNSTABLE 
    public abstract class SharedIsolatedStorageFile : IDisposable
    {
        protected Mutex FileLock;
        protected readonly IsolatedStorageFile IsolatedStorage;
        protected Stream FileStream;
        protected Boolean Disposed;
        private String idString;

        private const String MutexNamePrefix = "shf_";

        public SharedIsolatedStorageFile(String IdString)
        {
            idString = IdString;
            try
            {
                IsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication();
            }
            catch (IsolatedStorageException e)
            {
                throw new Exception("Unable to get user storage", e);
            }
            try
            {
                FileLock = new Mutex(false, MutexNamePrefix + IdString);
            }
            catch (Exception e)
            {
                throw new Exception("Unable to create file lock", e);
            }
        }
        public void OpenFile(String Filename)
        {
            try
            {
                FileStream = IsolatedStorage.OpenFile(Filename, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite, System.IO.FileShare.ReadWrite | System.IO.FileShare.Delete);
            }
            catch (Exception e)
            {
                throw new Exception("Unable to create/open " + Filename, e);
            }
        }
        public virtual void Dispose()
        {
            Dispose(true);
        }
        public virtual void Dispose(Boolean disposing)
        {
            if (disposing)
            {
                if (FileStream != null) FileStream.Close();
                FileLock.Close();
                Disposed = true;
            }
        }
        ~SharedIsolatedStorageFile()
        {
            Dispose(false);
        }
        public void LockResource()
        { FileLock.WaitOne(); }
        public void ReleaseResource()
        { FileLock.ReleaseMutex(); }
        public Boolean Check()
        {
            Boolean ready = true;
            if (FileStream == null) ready = false;
            if (Disposed) ready = false;
            return ready;
        }
    }
    public sealed class SharedSerializedXmlFile<T> : SharedIsolatedStorageFile
        where T : class
    {
        private T _value;
        private XmlSerializer Serializer;
        private Boolean changed = false;

        public String Name
        { get; private set; }
        public T Value
        {
            get
            {
                if (Check()) return _value;
                else return null;
            }
            set
            {
                if (Check())
                {
                    LockResource();
                    DataAcquireTime = DateTime.Now;
                    if (_value != value)
                    {
                        _value = value;
                        changed = true;
                    }
                    ReleaseResource();
                }
            }
        }
        public Boolean DataPresent
        {
            get
            {
                return _value != null;
            }
        }
        public DateTime DataAcquireTime
        { get; private set; }
        public DateTime LastSyncTime
        { get; private set; }
        public SharedSerializedXmlFile(String Filename)
            : base(Filename)
        {
            Name = Filename;
            _value = null;
            // Create file stream
            OpenFile(Filename);
            LastSyncTime = IsolatedStorage.GetLastWriteTime(Filename).DateTime;
            DataAcquireTime = LastSyncTime;
            // Process file
            TryLoad();
        }        
        public SharedSerializedXmlFile(Stream stream)
            : base("@stream@")
        {
            Name = "@stream@";
            // Register filestream
            FileStream = stream;
            LastSyncTime = DateTime.Now;
            DataAcquireTime = LastSyncTime;
            // Process stream
            TryLoad();
        }
        private void TryLoad()
        {
            Serializer = new XmlSerializer(typeof(T));
            if (FileStream.Length > 0) // if has data
                _value = (T)Serializer.Deserialize(FileStream);
        }
        public void Erase()
        {
            if (Check())
            {
                FileStream.Seek(0, System.IO.SeekOrigin.Begin);
                for (int i = 0; i < FileStream.Length; i++) FileStream.WriteByte(0);
                changed = true;
                FileStream.Seek(0, System.IO.SeekOrigin.Begin);
            }
        }
        public void Sync()
        {
            if (Check() && DataPresent && changed)
            {
                Erase();
                try
                {
                    LockResource();
                    Serializer.Serialize(FileStream, _value);
                }
                finally
                {
                    ReleaseResource();
                }
            }
        }
        public void CopyFromStream(System.IO.Stream Stream)
        {
            Value = (T)Serializer.Deserialize(Stream);
        }
    }
#endif
}
